// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <memory>
#include <utility>

namespace kumo::pollux::util {
    template<typename T>
    struct is_shared_ptr : std::false_type {
    };

    template<typename T>
    struct is_shared_ptr<std::shared_ptr<T> > : std::true_type {
    };

    template<template <typename> class Transformer, typename E>
    struct tuple_xform {
    };

    template<template <typename> class Transformer, typename... E>
    struct tuple_xform<Transformer, std::tuple<E...> > {
        using type = std::tuple<typename Transformer<E>::type...>;
    };

    // the following is c++17 backfill
    namespace detail {
        template<size_t...>
        struct seq {
        };

        template<size_t N, size_t... S>
        struct gens : gens<N - 1, N - 1, S...> {
        };

        template<size_t... S>
        struct gens<0, S...> {
            typedef seq<S...> type;
        };

        template<typename F, typename Tuple, size_t... S>
        auto apply_impl(F &&func, Tuple &&params, seq<S...>) {
            return func(std::get<S>(std::forward<Tuple>(params))...);
        }
    } // namespace detail

    template<typename F, typename Tuple>
    auto apply(F &&func, Tuple &&params) {
        return detail::apply_impl(
            std::forward<F>(func),
            std::forward<Tuple>(params),
            typename detail::gens<std::tuple_size<
                typename std::remove_reference<Tuple>::type>::value>::type());
    }

    template<class... Ts>
    struct overloaded : Ts... {
        using Ts::operator()...;
    };

    template<class... Ts>
    overloaded(Ts...) -> overloaded<Ts...>;

    namespace detail {
        template<typename...>
        struct voider {
            using type = void;
        };

        template<typename... T>
        using void_t = typename voider<T...>::type;

        template<typename T, typename U = void>
        struct is_mappish_impl : std::false_type {
        };

        template<typename T>
        struct is_mappish_impl<
                    T,
                    void_t<
                        typename T::key_type,
                        typename T::mapped_type,
                        decltype(std::declval<
                            T &>()[std::declval<const typename T::key_type &>()])> >
                : std::true_type {
        };
    } // namespace detail

    template<typename T>
    struct is_mappish : detail::is_mappish_impl<T>::type {
    };

    template<typename T>
    struct is_smart_pointer {
        static const bool value = false;
    };

    template<typename T>
    struct is_smart_pointer<std::shared_ptr<T> > {
        static const bool value = true;
    };

    template<typename T>
    struct is_smart_pointer<std::unique_ptr<T> > {
        static const bool value = true;
    };

    template<typename T>
    struct is_smart_pointer<std::weak_ptr<T> > {
        static const bool value = true;
    };

    // Checks if a class C provides a method which returns TRet and takes TArgs as
    // parameters. TResolver is a struct created using DECLARE_METHOD_RESOLVER,
    // which contains the name of the method.
    template<typename C, class TResolver, typename TRet, typename... TArgs>
    struct has_method {
    private:
        template<typename T>
        static constexpr auto check(T *) ->
            typename std::is_same<
                decltype(std::declval<TResolver>().template resolve<T>(
                    std::declval<TArgs>()...)),
                TRet>::type {
            return {};
        }

        template<typename>
        static constexpr std::false_type check(...) {
            return std::false_type();
        }

        using type = decltype(check<C>(nullptr));

    public:
        static constexpr bool value = type::value;
    };

    // Declares a method resolver to be used with has_method.
#define DECLARE_METHOD_RESOLVER(Name, MethodName)              \
  struct Name {                                                \
    template <class __T, typename... __TArgs>                  \
    constexpr auto resolve(__TArgs&&... args) const            \
        -> decltype(std::declval<__T>().MethodName(args...)) { \
      return {};                                               \
    }                                                          \
  }

    // Calling Name::resolve<T>::type will return T::TypeName if T::TypeName
    // exists, and otherwise will return T::OtherTypeName (it's existence is not
    // checked)
#define DECLARE_CONDITIONAL_TYPE_NAME(Name, TypeName, OtherTypeName) \
  struct Name {                                                      \
    template <typename __T, typename = void>                         \
    struct resolve {                                                 \
      using type = typename __T::OtherTypeName;                      \
    };                                                               \
                                                                     \
    template <typename __T>                                          \
    struct resolve<                                                  \
        __T,                                                         \
        std::void_t<decltype(sizeof(typename __T::TypeName))>> {     \
      using type = typename __T::TypeName;                           \
    };                                                               \
  }
} // namespace kumo::pollux::util
